﻿/*
 * Module Name: Forward Difference.cs
 * 
 * Purpose: This class is used to create a divided difference table and get approximations using Newton's Forward Differences Method
 * 
 * Created by: Chris Tisdale
 */


using System;
using System.Collections.Generic;

namespace Common_Code.Math_Extensions
{
// ReSharper disable once InconsistentNaming
    public class Forward_Differences : Interpolation<double>
    {
        /*
         * These are the Global Variables
         */
        readonly List<double> _x;
        readonly List<double> _y;
        readonly List<List<double>> _rows;
        double _h;

        /*
         * Name: Forward_Differences
         * 
         * Parameters: None
         * 
         * Returns: Object of Forward_Differences
         * 
         * Purpose: This creates a forward differences object
         */
        public Forward_Differences()
        {
            this._x = new List<double>();
            this._y = new List<double>();
            this._rows = new List<List<double>>();
            this._h = 0;
        }

        /*
         * Name: Forward_Differences
         * 
         * Parameters: double[] xPoints
         *             double[] yPoints
         * 
         * Returns: Object of Forward_Differences
         * 
         * Purpose: This creates a forward differences object
         */
        public Forward_Differences(double[] x, double[] y)
        {
            if (x.Length != y.Length)
                throw new NotSupportedException("The length's have to be the same");
            this._x = new List<double>();
            this._y = new List<double>();
            this._rows = new List<List<double>>();
            this._h = 0;
            GenTable(x, y);
        }

        /*
         * Name: GenTable
         * 
         * Parameters: double[] xPoints
         *             double[] yPoints
         * 
         * Returns: None
         * 
         * Purpose: This function assumes the Values are in order and are being appended to the end of the table
         */
        private void GenTable(IList<double> xPoints, IList<double> yPoints)
        {
            var subList = new List<double>();
// ReSharper disable once CompareOfFloatsByEqualityOperator
            if (_h == 0)
                _h = xPoints[1] - xPoints[0];
            for (var i = 0; i < xPoints.Count; i++)
            {
                this._x.Add(xPoints[i]);
                this._y.Add(yPoints[i]);
                for (var j = 0; j < (xPoints.Count - i - 1); j++)
                {
                    var temp = yPoints[j + 1] - yPoints[j];
                    if (i == 0)
                        _h = xPoints[i + 1] - xPoints[i];
// ReSharper disable once CompareOfFloatsByEqualityOperator
                    else if ((xPoints[i + 1] - xPoints[i]) != _h)
                        throw new NotSupportedException("The h is not consitent");
                    subList.Add(temp);
                }
                _rows.Add(subList);
                subList.Clear();
            }
        }

        /*
         * Name: GetInterpolationAt
         * 
         * Parameters: double val
         * 
         * Returns: double
         * 
         * Purpose: This gets the approximation to the point val
         */
        public double GetInterpolationAt(double val)
        {
            if (this._x.Count == 0)
                throw new NotSupportedException("The difference table has not been generated yet");
            var ans = _y[0] * _x[0];
            double mul = 1;
            double fac = 0;
            for (var i = 0; i < _rows.Count; i++)
            {
                mul *= (val - _x[i]);
                fac *= (i + 1);
                ans += (mul * _rows[i][0]) / fac;
            }
            return ans;
        }

        /*
         * Name: AddPoint
         * 
         * Parameters: double xPoints
         *             double yPoints
         * 
         * Returns: None
         * 
         * Purpose: This function assumes there is already a table generated
         */
        public void AddPoint(double x, double y)
        {
            if (this._x.Count == 0)
                throw new NotSupportedException();
            if (x < this._x[0])
            {
// ReSharper disable once CompareOfFloatsByEqualityOperator
                if ((this._x[0] - x) != _h)
                    throw new NotSupportedException("The h is not consitent");
                //Shift the list
                var tempX = this._x.ToArray();
                var tempY = this._y.ToArray();
                tempX = Common_Code.Heler_Functions.HelperClass.PlaceItemAtLoc<double>(tempX, x, 0);
                tempY = Common_Code.Heler_Functions.HelperClass.PlaceItemAtLoc<double>(tempY, y, 0);
                _rows.Clear();
                GenTable(tempX, tempY);
            }
            else if (x > this._x[this._x.Count - 1])
            {
// ReSharper disable once CompareOfFloatsByEqualityOperator
                if ((x - this._x[this._x.Count - 1]) != _h)
                    throw new NotSupportedException("The h is not consitent");
                var subList = new List<double>();
                this._x.Add(x);
                this._y.Add(y);
                double temp;
                int i;
                for (i = 0; i < (this._rows.Count - 1); i++)
                {
                    var loc = _rows[i].Count - 1;
                    temp = _rows[i][loc] - _rows[i][loc - 1];
                    _rows[i + 1].Add(temp);
                }
                temp = _rows[i][this._rows[i].Count - 1] - _rows[i][this._rows[i].Count - 2];
                subList.Add(temp);
                _rows.Add(subList);
            }
            else
                throw new NotSupportedException("The h is not consitent");
        }

        /*
         * Name: AddPoints
         * 
         * Parameters: double[] xPoints
         *             double[] yPoints
         * 
         * Returns: None
         * 
         * Purpose: This function assumes the Values are in order and are being appended to the end of the table
         */
        public void AddPoints(double[] x, double[] y)
        {
            if (x.Length != y.Length)
                throw new NotSupportedException("The length's have to be the same");
            if (this._x.Count != 0)
            {
                for (var i = 0; i < (x.Length - 1); i++)
                {
                    AddPoint(x[i], y[i]);
                }
            }
            else
                GenTable(x, y);
        }
    }
}
